package org.atlantis.grosbeak.lang;

import java.util.Collection;
import java.util.LinkedList;

/**
 * 字符串操作帮助类。
 * 
 * @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
 * 
 */
public abstract class Strings {

	/**
	 * 将字符串的首字母大写，如果已经是大写了就直接返回。
	 * 
	 * @param cs
	 *            要将首字母大写的字符串。
	 * @return 首字母大写后的新字符串。
	 */
	public static String capitalize(CharSequence cs) {
		if (null == cs)
			return null;
		int length = cs.length();
		if (length == 0)
			return "";
		char initial = cs.charAt(0);
		if (Character.isUpperCase(initial))
			return cs.toString();

		StringBuilder sb = new StringBuilder(length);
		sb.append(Character.toUpperCase(initial)).append(
				cs.subSequence(1, length));
		return sb.toString();
	}

	/**
	 * 将字符串首字母大写，如果已经是大写了就直接返回（忽略但仍保留空白字符）。
	 * 
	 * @param cs
	 *            要将首字母大写的字符串。
	 * @return 首字母大写后的新字符串。
	 */
	public static String capitalizeKeepBlank(CharSequence cs) {
		if (null == cs)
			return null;
		int length = cs.length();
		if (length == 0)
			return "";

		StringBuilder sb = new StringBuilder(length);
		int index = 0;
		for (; index < length; index++) {
			char c = cs.charAt(index);
			if (!Character.isWhitespace(c)) {
				if (Character.isUpperCase(c)) {
					return cs.toString();
				} else {
					sb.append(Character.toUpperCase(c));
					break;
				}
			}
			sb.append(c);
		}
		sb.append(cs.subSequence(index + 1, length));
		return sb.toString();
	}

	/**
	 * 将字符串的首字母小写，如果已经是小写了就直接返回。
	 * 
	 * @param cs
	 *            要将首字母小写的字符串。
	 * @return 首字母小写后的新字符串。
	 */
	public static String unCapitalize(CharSequence cs) {
		if (null == cs)
			return null;
		int length = cs.length();
		if (length == 0)
			return "";
		char initial = cs.charAt(0);
		if (Character.isLowerCase(initial))
			return cs.toString();

		StringBuilder sb = new StringBuilder(length);
		sb.append(Character.toLowerCase(initial)).append(
				cs.subSequence(1, length));
		return sb.toString();
	}

	/**
	 * 将字符串的首字母小写，如果已经是小写了就直接返回（忽略但仍保留空白字符）。
	 * 
	 * @param cs
	 *            要将首字母小写的字符串。
	 * @return 首字母小写后的新字符串。
	 */
	public static String unCapitalizeKeepBlank(CharSequence cs) {
		if (null == cs)
			return null;
		int length = cs.length();
		if (length == 0)
			return "";

		StringBuilder sb = new StringBuilder(length);
		int index = 0;
		for (; index < length; index++) {
			char c = cs.charAt(index);
			if (!Character.isWhitespace(c)) {
				if (Character.isLowerCase(c)) {
					return cs.toString();
				} else {
					sb.append(Character.toLowerCase(c));
					break;
				}
			}
			sb.append(c);
		}
		sb.append(cs.subSequence(index + 1, length));
		return sb.toString();
	}

	/**
	 * 判断字符串是否为空。
	 * 
	 * @param cs
	 *            字符串。
	 * @return 是否是空字符串。
	 */
	public static boolean isEmpty(CharSequence cs) {
		if (null == cs)
			return true;
		return cs.length() == 0;
	}

	/**
	 * 判断字符串是否为一个空白字符串。
	 * 
	 * @param cs
	 *            字符串。
	 * @return 是否为空白字符串
	 */
	public static boolean isBlank(CharSequence cs) {
		if (null == cs)
			return true;
		int length = cs.length();
		for (int i = 0; i < length; i++) {
			char c = cs.charAt(i);
			if (!Character.isWhitespace(c))
				return false;
		}
		return true;
	}

	/**
	 * 去掉字符串前后的空白。
	 * 
	 * @param cs
	 *            字符串。
	 * @return 去掉前后空白的新字符串。
	 */
	public static String trim(CharSequence cs) {
		if (null == cs)
			return null;
		int length = cs.length();
		if (length == 0)
			return cs.toString();
		int l = 0;
		int last = length - 1;
		int r = last;
		for (; l < length; l++) {
			char c = cs.charAt(l);
			if (!Character.isWhitespace(c))
				break;
		}
		for (; r > 0; r--) {
			char c = cs.charAt(r);
			if (!Character.isWhitespace(c))
				break;
		}
		if (l > r)
			return "";
		else if (l == 0 && r == last)
			return cs.toString();

		if (cs instanceof String)
			return ((String) cs).substring(l, r + 1);
		return cs.subSequence(l, r + 1).toString();
	}

	/**
	 * 根据半角逗号，把字符串拆分成数组并忽略其中的空白字符
	 * 
	 * @param str
	 *            要拆分的字符串。
	 * @return 拆分后的字符串数组。
	 */
	public static String[] spliteIgnoreBlank(String str) {
		return spliteIgnoreBlank(str, ",");
	}

	/**
	 * 根据正则表达式，将字符串拆分成数组并忽略其中的空白字符。
	 * 
	 * @param str
	 *            要拆分的字符串。
	 * @param regex
	 *            正则表达式。
	 * @return 拆分后的字符串数组。
	 */
	public static String[] spliteIgnoreBlank(String str, String regex) {
		if (null == str)
			return null;
		String[] strs = str.split(regex);
		LinkedList<String> list = new LinkedList<String>();
		for (String s : strs) {
			if (isBlank(s))
				continue;
			list.add(trim(s));
		}
		String[] re = new String[list.size()];
		return list.toArray(re);
	}

	/**
	 * 删除字符串第一个字符。
	 * 
	 * @param str
	 *            要删除第一个字符的字符串。
	 * @return 删除第一个字符后的新字符串。
	 */
	public static String removeFirst(String str) {
		if (null == str)
			return null;
		if (str.length() > 1)
			return str.substring(1);
		return "";
	}

	/**
	 * 删除字符串的前n个字符。
	 * 
	 * @param str
	 *            要删除字符的字符串。
	 * @param n
	 *            要删除字符的个数。
	 * @return 删除前n个字符后的新字符串。
	 */
	public static String removeFirst(String str, int n) {
		if (null == str)
			return null;
		if (str.length() > n)
			return str.substring(n);
		return "";
	}

	/**
	 * 删除字符串最后一个字符。
	 * 
	 * @param str
	 *            要删除最后一个字符的字符串。
	 * @return 删除最后一个字符的新字符串。
	 */
	public static String removeLast(String str) {
		if (null == str)
			return null;
		int length = str.length();
		if (length > 1)
			return str.substring(0, length - 1);
		return "";
	}

	/**
	 * 删除字符串最后的n个字符。
	 * 
	 * @param str
	 *            要删除字符的字符串。
	 * @param n
	 *            要删除字符的个数。
	 * @return 删除后n个字符的字符串。
	 */
	public static String removeLast(String str, int n) {
		if (null == str)
			return null;
		int length = str.length();
		if (length > n)
			return str.substring(0, length - n);
		return "";
	}

	/**
	 * 复制字符串。
	 * 
	 * @param cs
	 *            要复制的字符串。
	 * @param num
	 *            复制的次数。
	 * @return 复制后的新字符串。
	 */
	public static String duplicate(CharSequence cs, int num) {
		if (isEmpty(cs))
			return "";
		StringBuilder sb = new StringBuilder(num * cs.length());
		for (int i = 0; i < num; i++)
			sb.append(cs);
		return sb.toString();
	}

	/**
	 * 复制字符。
	 * 
	 * @param c
	 *            要复制的字符。
	 * @param num
	 *            复制的次数。
	 * @return 复制后的新字符串。
	 */
	public static String duplicate(char c, int num) {
		if (c == 0)
			return "";
		StringBuilder sb = new StringBuilder(num);
		for (int i = 0; i < num; i++)
			sb.append(c);
		return sb.toString();
	}

	/**
	 * 从一个字符串集合中找出长度最长的字符串的长度。
	 * 
	 * @param css
	 *            字符串集合。
	 * @return 最长字符串的长度。
	 */
	public static int maxLen(Collection<? extends CharSequence> css) {
		int length = 0;
		if (null != css)
			for (CharSequence cs : css)
				if (null != cs)
					length = Math.max(length, cs.length());
		return length;
	}

	/**
	 * 从一个字符串数组中找出长度最长的字符串的长度。
	 * 
	 * @param array
	 *            字符串数组。
	 * @return 最长字符串的长度。
	 */
	public static <T extends CharSequence> int maxLen(T[] array) {
		int length = 0;
		if (null != array)
			for (CharSequence cs : array)
				if (null != cs)
					length = Math.max(length, cs.length());
		return length;
	}

	/**
	 * 从一个字符串结合中找出长度最小的字符串的长度。
	 * 
	 * @param css
	 *            字符串集合。
	 * @return 最短字符串的长度。
	 */
	public static int minLen(Collection<? extends CharSequence> css) {
		int length = 0;
		if (null != css)
			for (CharSequence cs : css)
				if (null != cs)
					length = Math.min(length, cs.length());
		return length;
	}

	/**
	 * 从一个字符串数组中找出长度最小的字符串的长度。
	 * 
	 * @param array
	 *            字符串数组。
	 * @return 最短字符串的长度。
	 */
	public static <T extends CharSequence> int minLen(T[] array) {
		int length = 0;
		if (null != array)
			for (CharSequence cs : array)
				if (null != cs)
					length = Math.min(length, cs.length());
		return length;
	}

	/**
	 * 对obj进行toString()操作，如果obj为null则返回""。
	 * 
	 * @param obj
	 *            要进行toString()操作的对象
	 * @return 表示obj的字符串。
	 */
	public static String sNull(Object obj) {
		return sNull(obj, "");
	}

	/**
	 * 对obj进行toString()操作，如果obj为null则返回def中的默认值。
	 * 
	 * @param obj
	 *            要进行toString()操作的对象。
	 * @param cs
	 *            默认值。
	 * @return 表示obj的字符串。
	 */
	public static String sNull(Object obj, CharSequence def) {
		return (obj != null) ? obj.toString() : def.toString();
	}

	/**
	 * 通过sep连接符来连接字符串，如果sep为null，那么就直接把字符串连接起来。
	 * 
	 * @param objs
	 *            需要连接的对象数组（程序会对这些对象进行sNull(Object obj)处理）。
	 * @param sep
	 *            连接符，如：“,” “-” “!”等。
	 * @return 连接后的字符串。
	 */
	public static String join(Object[] objs, String sep) {
		if (null == objs)
			return null;
		if (null == sep)
			sep = "";

		StringBuilder sb = new StringBuilder(objs.length);
		int length = objs.length;
		for (int i = 0; i < length; i++) {
			String str = sNull(objs[i]);
			if ("".equals(str))
				continue;
			if (i == length - 1) {
				sb.append(str);
				break;
			}
			sb.append(str);
			sb.append(sep);
		}
		return sb.toString();
	}

	/**
	 * 不使用连接符，直接把字符串连接起来。
	 * 
	 * @param objs
	 *            需要连接的对象数组（程序会对这些对象进行sNull(Object obj)处理）。
	 * @return 连接后的字符串。
	 */
	public static String join(Object[] objs) {
		return join(objs, null);
	}

	/**
	 * 字符串是否以前缀（prefix）开始，保证在字符串为null的情况下安全。
	 * 
	 * @param str
	 *            字符串
	 * @param prefix
	 *            前缀
	 * @return str是否以prefix开始。
	 */
	public static boolean startsWith(String str, String prefix) {
		return startsWith(str, prefix, false);
	}

	/**
	 * 字符串是否以前缀（prefix）开始，保证在字符串为null的情况下安全，并忽略大小写。
	 * 
	 * @param str
	 *            字符串
	 * @param prefix
	 *            前缀
	 * @return str是否以prefix开始。
	 */
	public static boolean startsWithIgnoreCase(String str, String prefix) {
		return startsWith(str, prefix, true);
	}

	/**
	 * 字符串是否以前缀（prefix）开始，保证在字符串为null的情况下安全，并且可以决定是否忽略大小写。
	 * 
	 * @param str
	 *            字符串
	 * @param prefix
	 *            前缀
	 * @param ignoreCase
	 *            是否忽略大小写。true表示忽略，false表示不忽略。
	 * @return str是否以prefix开始。
	 */
	public static boolean startsWith(String str, String prefix,
			boolean ignoreCase) {
		if (str == null || prefix == null)
			return false;
		if (prefix.length() > str.length())
			return false;
		return str.regionMatches(ignoreCase, 0, prefix, 0, prefix.length());
	}

	/**
	 * 字符串是否以后缀（suffix）结尾，保证在字符串为null的情况下安全。
	 * 
	 * @param str
	 *            字符串
	 * @param suffix
	 *            后缀
	 * @return str是否以suffix结尾。
	 */
	public static boolean endsWith(String str, String suffix) {
		return endsWith(str, suffix, false);
	}

	/**
	 * 字符串是否以后缀（suffix）结尾，保证在字符串为null的情况下安全，并且忽略大小写。
	 * 
	 * @param str
	 *            字符串
	 * @param suffix
	 *            后缀
	 * @return str是否以suffix结尾。
	 */
	public static boolean endsWithIgnoreCase(String str, String suffix) {
		return endsWith(str, suffix, true);
	}

	/**
	 * 字符串是否以后缀suffix结尾，保证在字符串为null的情况下安全，并可以决定是否忽略大小写。
	 * 
	 * @param str
	 *            字符串
	 * @param suffix
	 *            后缀
	 * @param ignoreCase
	 *            是否忽略大小写。true表示忽略，false表示不忽略。
	 * @return str是否以suffix结尾。
	 */
	public static boolean endsWith(String str, String suffix, boolean ignoreCase) {
		if (str == null || suffix == null)
			return false;
		if (suffix.length() > str.length())
			return false;
		int strOffset = str.length() - suffix.length();
		return str.regionMatches(ignoreCase, strOffset, suffix, 0, suffix
				.length());
	}

	/**
	 * 判断一个字符串是否被一个指定的最左边字符和最右边字符包裹。
	 * 
	 * @param cs
	 *            字符串
	 * @param leftC
	 *            最左边的字符。
	 * @param rightC
	 *            最右边的字符。
	 * @return 字符串是否被一个最左、又边的两个字符包裹。
	 */
	public static boolean isWrapedBy(CharSequence cs, char leftC, char rightC) {
		if (null == cs)
			return false;
		int length = cs.length();
		if (length < 2)
			return false;
		if (cs.charAt(0) != leftC)
			return false;
		if (cs.charAt(length - 1) != rightC)
			return false;
		return true;
	}

	/**
	 * 判断一个字符串是否被一个指定的最左边字符和最右边字符包裹（会自动去除左右两边的空字符）。
	 * 
	 * @param cs
	 *            字符串
	 * @param leftC
	 *            最左边的字符。
	 * @param rightC
	 *            最右边的字符。
	 * @return 字符串是否被一个最左、又边的两个字符包裹。
	 */
	public static boolean isWrapedByIgnoreBlank(CharSequence cs, char leftC,
			char rightC) {
		if (null == cs)
			return false;
		int length = cs.length();
		if (length < 2)
			return false;

		int leftIndex = 0;
		int rightIndex = length - 1;
		for (; leftIndex < length; leftIndex++) {
			char c = cs.charAt(leftIndex);
			if (!Character.isWhitespace(c))
				break;
		}
		for (; rightIndex > 0; rightIndex--) {
			char c = cs.charAt(rightIndex);
			if (!Character.isWhitespace(c))
				break;
		}
		if (leftIndex >= rightIndex)
			return false;
		else if (cs.charAt(leftIndex) != leftC)
			return false;
		else if (cs.charAt(rightIndex) != rightC)
			return false;
		return true;
	}

	/**
	 * 保证字符串（str）为固定的长度（width）。超过长度，则从右边切除，不够则在右边补充一定的字符。
	 * 
	 * @param s
	 *            固定长度的字符串。
	 * @param width
	 *            字符串的固定长度。
	 * @param c
	 *            要补充的字符。
	 */
	public static String cutRight(String str, int width, char c) {
		if (null == str)
			return null;
		int length = str.length();
		if (length == width)
			return str;
		if (length < width)
			return str + Strings.duplicate(c, width - length);
		return str.substring(0, width);
	}

	/**
	 * 保证字符串（str）为固定的长度（width）。超过长度，则从左边切除，不够则在左边补充一定的字符。
	 * 
	 * @param s
	 *            固定长度的字符串。
	 * @param width
	 *            字符串的固定长度。
	 * @param c
	 *            要补充的字符。
	 */
	public static String cutLeft(String str, int width, char c) {
		if (null == str)
			return null;
		int length = str.length();
		if (length == width)
			return str;
		if (length < width)
			return Strings.duplicate(c, width - length) + str;
		return str.substring(length - width, length);
	}

	/**
	 * 对字符串进行截取，如果字符串的长度超过规定长度则用...填充。规定长度是截取后字符串的长度加3，因为...占3个长度。如果字符串长度小于规定的长度
	 * ，那么直接返回字符串。 注意：width的长度不能小于4，因为...要占三个长度，如果width小于4就只能得到...了，这是没有意义的，
	 * 所以如果width小于4就会抛出一个IllegalArgumentException异常。
	 * 
	 * @param str
	 *            要进行截取的字符串。
	 * @param width
	 *            字符串的规定长度。
	 * @return 截取后的新字符串。
	 */
	public static String abbreviate(String str, int width) {
		if (null == str)
			return null;
		if (width < 4)
			throw new IllegalArgumentException(
					"width is too short. The shortest width is 4.");
		if (str.length() <= width)
			return str;
		return str.substring(0, (width - 3)) + "...";
	}

	/**
	 * 把一个16进制的值转换成字符串。
	 * 
	 * @param hex
	 *            16进制的值。
	 * @return 由16进制的值转换而来的字符串。
	 */
	public static String fromHexToString(String hex) {
		if (null == hex)
			return null;
		if (isEmpty(hex) || isBlank(hex))
			return "";
		if (startsWithIgnoreCase(hex, "0x"))
			hex = hex.substring(2);
		return "" + (char) Integer.parseInt(hex, 16);
	}

	/**
	 * 找出两个字符串中不同的字符串子集。准确来说是str2相对于str1中不同的字符，str1是一个用来比较的参考字符串。
	 * 返回的结果也是str2中的子字符串。
	 * 
	 * @param str1
	 *            字符串1。
	 * @param str2
	 *            字符串2。
	 * @return 两个字符串中不同的字符串子集。如果str2中所有的字符都和str1相同，则返回""。
	 */
	public static String difference(String str1, String str2) {
		if (str1 == null)
			return str2;
		if (str2 == null)
			return str1;
		int at = indexOfDifference(str1, str2);
		if (at == -1)
			return "";
		return str2.substring(at);
	}

	/**
	 * 找出两个字符串不同的位置。准确来说是str2和str1来比较，返回的位置是str2中开始出现和str1中不同字符的位置，位置是相对于str2来说的
	 * ，str1是一个用来比较的参考字符串。
	 * 
	 * @param str1
	 *            字符串1。
	 * @param str2
	 *            字符串2。
	 * @return 两个字符串出现不同字符的位置。如果返回值为-1则表示两个字符串相同，如果返回值为0则表示两个字符串中有一个字符串为null。
	 */
	public static int indexOfDifference(String str1, String str2) {
		if (str1 == str2)
			return -1;
		if (str1 == null || str2 == null)
			return 0;
		int i;
		for (i = 0; i < str1.length() && i < str2.length(); ++i)
			if (str1.charAt(i) != str2.charAt(i))
				break;
		if (i < str2.length() || i < str1.length())
			return i;
		return -1;
	}

	/**
	 * 比较两个字符串是否相等，在字符串为null的时候这个方法是安全的。
	 * 
	 * @param str1
	 *            字符串1。
	 * @param str2
	 *            字符串2。
	 * @return 两个字符串是否相等。
	 */
	public static boolean equals(String str1, String str2) {
		if (str1 == null || str2 == null)
			return false;
		return str1.equals(str2);
	}

	/**
	 * 比较两个字符串是否相等，忽略大小写，在字符串为null的时候这个方法是安全的。
	 * 
	 * @param str1
	 * @param str2
	 * @return
	 */
	public static boolean equalsIgnoreCase(String str1, String str2) {
		if (str1 == null || str2 == null)
			return false;
		return str1.equalsIgnoreCase(str2);
	}

	/**
	 * 删除str中的所有空白字符。
	 * 
	 * @param str
	 *            要删除空白字符的字符串。
	 * @return 删除空白字符后的新字符串。
	 */
	public static String deleteBlank(String str) {
		if (isEmpty(str))
			return str;
		int length = str.length();
		char[] cs = new char[length];
		int csIndex = 0;
		for (int i = 0; i < length; i++)
			if (!Character.isWhitespace(str.charAt(i)))
				cs[csIndex++] = str.charAt(i);
		if (csIndex == length)
			return str;
		return new String(cs, 0, csIndex);
	}

	/**
	 * 查找子字符串sub在字符串str中出现的次数。
	 * 
	 * @param str
	 *            字符串。
	 * @param sub
	 *            子字符串。
	 * @return 子字符串sub在字符串str中出现的次数。
	 */
	public static int countMatches(String str, String sub) {
		if (isEmpty(str) || isEmpty(sub))
			return 0;
		int count = 0;
		int idx = 0;
		while ((idx = str.indexOf(sub, idx)) != -1) {
			count++;
			idx += sub.length();
		}
		return count;
	}
}
